# 一、原子性操作方法
# 1.1 同步锁
synchronized (this){
++ count;
}
1
2
3
2
3
# 1.2 Atomic类
- Atomic底层利用CAS实现,而CAS则是利用UnSafe类(native) + 反射技术实现,通过
do...while(自旋)的模式,每次都会检查变量是否被修改过,修改过则while成立并重做。
private AtomicLong acount = new AtomicLong(0L);
acount.incrementAndGet();
1
2
2
# 1.3 LongAdder类
- LongAdder为JDK8提供的多线程技术类,底层是利用分而治之的思想,即为每个线程分配一个计数副本,线程之间的副本相互隔离,最终将所有副本相加,多线程下计数速度最快。
private LongAdder lacount = new LongAdder();
// 计数
lacount.increment();
// 获取结果
lacount.sum()
1
2
3
4
5
2
3
4
5
# 1.4 Atomic类实现原理-CAS
- 通过
Unsafe获取对象属性偏移量
- 通过
- 利用
Unsafe的CMS方法及获取到的偏移量实现自增(操作内存)
- 利用
public class LockDemo1 {
private volatile int value = 0;
// 直接操作内存,修改对象、数组内存等
private static Unsafe unsafe;
// 获取属性偏移量
private static long valueOfferset;
static {
try {
//反射技术获取unsafe值
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (Unsafe)field.get(null);
// 获取到value属性偏移量(用于定位value属性在内存中的具体地址)
valueOfferset = unsafe.objectFieldOffset(LockDemo1.class.getDeclaredField("value"));
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
public void add() {
// i++; 在Java层面分为三个步骤
// CAS + 循环重试(自旋)
int current;
do {
// 操作耗时的话线程会占用大量的CPU执行时间
current = unsafe.getIntVolatile(this, valueOfferset);
} while (!unsafe.compareAndSwapInt(this, valueOfferset, current, current + 1));
// 执行失败则重试
}
public static void main(String[] args) throws InterruptedException {
LockDemo1 lockDemo1 = new LockDemo1();
for (int i = 0; i < 2; i++) {
new Thread(() -> {
for (int j = 0; j < 10000; j++) {
lockDemo1.add();
}
}).start();
}
Thread.sleep(2000L);
System.out.println(lockDemo1.value);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# 二、死锁
# 2.1 死锁的概念
在多线程编程中,我们为了防止多线程竞争共享资源而导致数据错乱,都会在操作共享资源之前加上互斥锁,只有成功获得到锁的线程,才能操作共享资源,获取不到锁的线程就只能等待,直到锁被释放。
那么,当两个线程为了保护两个不同的共享资源而使用了两个互斥锁,那么这两个互斥锁应用不当的时候,可能会造成两个线程都在等待对方释放锁,在没有外力的作用下,这些线程会一直相互等待,就没办法继续运行,这种情况就是发生了死锁。
# 2.2 死锁的条件
# 互斥条件
同一个资源在任何时刻最多只能被一个线程持有

# 持有并等待条件
持有锁1的线程会等待锁2,等待过程不会释放自己持有的锁

# 不可剥夺条件
线程A持有的锁只能由自己释放,无法被其他线程释放。

# 环路等待条件
即至少需要两个锁资源,并且存在多线程抢占,最终形成了环形链。

# 2.3 死锁的排查
# jps + jstack排查死锁问题
- jps查看当前正运行的java进程

- jstack + 进程号,可以查看当前的线程栈信息,定位到死锁的位置并进行优化

# jconsole排查死锁问题


# VisualVM排查死锁问题


点击“线程Dump”按钮,dump下堆栈信息,定位产生死锁的代码位置

# 2.4 如何避免死锁
避免死锁的方式就是打破上面产生死锁的其中一个条件
- 打破持有并等待条件:超时释放锁
- 打破不可剥夺条件:可强制释放锁
- 打破环路等待条件:资源有序分配法
资源有序分配是最常用的方法,即对于需要获取A,B两个锁的所有线程,在代码层面上都通过先获取A锁,再获取B锁,保证不会出现环路。
